use chrono::prelude::*;
use plotters::prelude::*;
use serde::Deserialize;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = format!(
        "https://api.bybit.com/v5/market/kline?category={category}&symbol={symbol}&interval={interval}&limit={limit}",
        category = "linear",
        symbol="BTCUSD",
        interval=5,
        limit=1000
    );
    let resp = reqwest::get(url)
        .await?
        .json::<ByBitResponse<KlineSequence>>()
        .await?;

    let dir = "plots-output";
    let filepath = format!("{}/sma15.png", &dir);
    let root = BitMapBackend::new(&filepath, (1280, 960)).into_drawing_area();
    root.fill(&WHITE).expect("Error filling background.");


    let data = resp.result.list;
    // Convert timestamp to Date<Local>
    let data: Vec<(DateTime<Local>, f32, f32, f32, f32)> = data
        .iter()
        .map(|x| (timestamp_to_local_date(&x[0]),
                  x[1].parse::<f32>().expect("Valid float number"),
                  x[2].parse::<f32>().expect("Valid float number"),
                  x[3].parse::<f32>().expect("Valid float number"),
                  x[4].parse::<f32>().expect("Valid float number")))
        .collect();

    // Basic chart configuration
    let mut chart = ChartBuilder::on(&root)
        .x_label_area_size(60)
        .y_label_area_size(60)
        .caption(
            "Candles + simple moving average",
            ("sans-serif", 50.0).into_font(),
        )
        .build_cartesian_2d(data[0].0..data[data.len() - 1].0, 36500f32..38500f32)
        .unwrap();
    chart
        .configure_mesh()
        .light_line_style(WHITE)
        .draw()
        .unwrap();

    chart
        .draw_series(data.iter().map(|x| {
            CandleStick::new(
                x.0,
                x.1,
                x.2,
                x.3,
                x.4,
                RGBColor(98, 209, 61).filled(),
                RGBColor(209, 61, 61).filled(),
                10,
            )
        }))
        .unwrap();

    let close_price_data: Vec<f64> = data.iter().map(|x| x.4 as f64).collect();
    let sma_data = simple_moving_average(&close_price_data, 15).expect("Calculating SMA failed");
    let mut line_data: Vec<(DateTime<Local>, f32)> = Vec::new();
    for i in 0..sma_data.len() {
        line_data.push((data[i].0, sma_data[i] as f32));
    }

    chart
        .draw_series(LineSeries::new(line_data, BLUE.stroke_width(2)))
        .unwrap()
        .label("SMA 15")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));

    let sma_data = simple_moving_average(&close_price_data, 30).expect("Calculating SMA failed");
    let mut line_data: Vec<(DateTime<Local>, f32)> = Vec::new();
    for i in 0..sma_data.len() {
        line_data.push((data[i].0, sma_data[i] as f32));
    }
    chart
        .draw_series(LineSeries::new(
            line_data,
            RGBColor(150, 50, 168).stroke_width(2),
        ))
        .unwrap()
        .label("SMA 30")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RGBColor(150, 50, 168)));

    chart
        .configure_series_labels()
        .position(SeriesLabelPosition::UpperMiddle)
        .label_font(("sans-serif", 30.0).into_font())
        .background_style(WHITE.filled())
        .draw()
        .unwrap();

    root.present().unwrap_or_else(|_| panic!("Unable to write result to file please make sure directory '{}' exists under the current dir", &dir));
    println!("Plot has been saved to {}", &filepath);

    Ok(())
}


pub fn timestamp_to_local_date(timestamp_millis: &String) -> DateTime<Local> {
    let timestamp = timestamp_millis.parse::<i64>().expect("Valid number as timestamp");
    Local.timestamp_millis_opt(timestamp).earliest().expect("Valid timestamp")
}

pub fn simple_moving_average(data_set: &Vec<f64>, window_size: usize) -> Option<Vec<f64>> {
    if window_size > data_set.len() {
        return None;
    }
    let mut result: Vec<f64> = Vec::new();
    let mut window_start = 0;
    while window_start + window_size <= data_set.len() {
        let window_end = window_start + window_size;
        let data_slice = &data_set[window_start..window_end];
        let sum: f64 = data_slice.iter().sum();
        let average = sum / window_size as f64;
        result.push(average);
        window_start += 1;
    }
    Some(result)
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ByBitResponse<T> {
    pub ret_code: i16,
    pub ret_msg: String,
    pub result: T
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct KlineSequence {
    pub symbol: String,
    pub category: String,
    pub list: Vec<Vec<String>>
}
